package TrackModel;

import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

import javax.annotation.processing.Processor;

import TrainModel.TrainModel;
import application.Main;

/**
 * @author Jordan
 *Class that holds all of the Block objects for an entire line of the railroad system in an ArrayList of Blocks. 
 *The Line class also stores all of the Train, StationBlock, and ControllerBlock objects in ArrayLists for
 *easy access and manipulation.
 */
public class Line implements TrackControllerToTrackModel
{
	private String name, filename;
	private ArrayList<Block> theLine;
	private ArrayList<StationBlock> theStations;
	private ArrayList<ControllerBlock> theControllers;
	private ArrayList<Train> theTrains;
	private int numBlocks, numStations;
	private Random randomNumber = new Random();
	
	/**Constructor that initializes all of the ArrayLists and calls the buildFromFile() function
	 * @param name - The name of the Line
	 * @param filename - The name of the file that the Line will be built from
	 */
	public Line(String name, String filename)
	{
		this.name = name;
		this.filename = "/" + filename;
		theLine = new ArrayList<Block>();
		theStations = new ArrayList<StationBlock>();
		theControllers = new ArrayList<ControllerBlock>();
		theTrains = new ArrayList<Train>();
		buildFromFile();
	}
	
	/**Method that builds the Line from a file line-by-line. Each line of the file represents
	 * a Block object that is created and added to the ArrayList theLine. Also builds the
	 * ArrayLists of Controller Blocks and Station Blocks and initializes all of the Track Controllers
	 * for the Line.
	 */
	private void buildFromFile()
	{
		Scanner inScan;
		try
		{
			inScan = new Scanner(Processor.class.getResourceAsStream(this.filename), "UTF-8");
			while (inScan.hasNextLine())	// Get every line of the file
			{
				String theBlockString = inScan.nextLine();
				String[] parts = theBlockString.split("\\|"); // Split the String up into an array of smaller Strings
				if (parts[11].equals("0")) // Standard Block object
				{
					Block theBlock = new Block(parts);
					theLine.add(theBlock);
				}
				else if (parts[11].equals("1")) // Crossing Block object
				{
					CrossingBlock theBlock = new CrossingBlock(parts);
					theLine.add(theBlock);
				}
				else if (parts[11].equals("2")) // Switch Block object
				{
					SwitchBlock theBlock = new SwitchBlock(parts);
					theLine.add(theBlock);
				}
				else if (parts[11].equals("3")) // Controller Block object
				{
					ControllerBlock theBlock = new ControllerBlock(parts);
					theLine.add(theBlock);
					theControllers.add(theBlock);
				}
				else // Station Block object
				{
					StationBlock theBlock = new StationBlock(parts);
					theLine.add(theBlock);
					theStations.add(theBlock);
					numStations++;
				}
				numBlocks++;
			}
			inScan.close();
			for (int i = 0; i < theControllers.size(); i++) // Iterate through all of the Controller Blocks
			{
				ControllerBlock theController = theControllers.get(i);
				int[][] theRanges = theController.getBlockRanges(); // Get the array containing the block ranges for this Controller Block
				for (int j = 0; j < theRanges.length; j++)
				{
					int startBlock = theRanges[j][0];
					int endBlock = theRanges[j][1];
					for (int k = startBlock - 1; k < endBlock; k++) // Add all of the Block objects to the Controller that this Controller is in charge of
					{
						theController.createBlocksInRange(theLine.get(k));
					}
				}
				theController.initializeTrackController(); // Initialize the Track Controller object
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
	}
	
	/* (non-Javadoc)
	 * @see TrackModel.TrackControllerToTrackModel#toggleSwitch(TrackModel.Block)
	 * Method called by a Track Controller to toggle the state of a Switch
	 */
	public boolean toggleSwitch(Block theSwitch)
	{
		SwitchBlock mySwitch = (SwitchBlock)theLine.get(theSwitch.getBlockID() - 1);
		return mySwitch.toggleSwitch();
	}
	
	/* (non-Javadoc)
	 * @see TrackModel.TrackControllerToTrackModel#toggleRailwayCrossing(TrackModel.Block)
	 * Method called by a Track Controller to toggle the state of a Crossing
	 */
	public boolean toggleRailwayCrossing(Block theCrossing)
	{
		CrossingBlock myCrossing = (CrossingBlock)theLine.get(theCrossing.getBlockID() - 1);
		return myCrossing.toggleCrossing();
	}
	
	/* (non-Javadoc)
	 * @see TrackModel.TrackControllerToTrackModel#checkTrainPresence(int)
	 * Method called by a Track Controller to see what Blocks have Train Models on them
	 */
	public boolean[] checkTrainPresence(int controllerID)
	{
		ArrayList<Block> controllerBlocks = theControllers.get(controllerID - 1).getControllerBlocks();
		boolean[] trainPresence = new boolean[controllerBlocks.size()];
		for (int i = 0; i < controllerBlocks.size(); i++)
		{
			trainPresence[i] = controllerBlocks.get(i).hasTrain;
		}
		return trainPresence;
	}
	
	/* (non-Javadoc)
	 * @see TrackModel.TrackControllerToTrackModel#isHealthy(TrackModel.Block)
	 * Method called by a Track Controller to see if a Block is functioning normally
	 */
	public boolean isHealthy(Block theBlock)
	{
		Block myBlock = theLine.get(theBlock.getBlockID() - 1);
		if (myBlock.hasPowerFailure || myBlock.hasBrokenRail || myBlock.hasCircuitFailure)
			return false;
		else
			return true;
	}
	
	/* (non-Javadoc)
	 * @see TrackModel.TrackControllerToTrackModel#checkSpeedLimit(TrackModel.Block)
	 * Method called by a Track Controller to check the Speed Limit on a particular Block
	 */
	public double checkSpeedLimit(Block theBlock)
	{
		return theLine.get(theBlock.getBlockID() - 1).getSpeedLimit();
	}
	
	/**
	 * @return Returns the name of the Line
	 */
	public String getName()
	{
		return name;
	}
	
	/**
	 * @param blockID - The ID of the Block being requested
	 * @return Returns the requested Block object
	 */
	public Block getBlock(int blockID)
	{
		return theLine.get(blockID - 1);
	}
	
	/**Method used by the CTC Office to get the next Block of a Line
	 * @param currBlock - The current Block from which you would like the next Block
	 * @return Returns the first Block of the Red Line or the Green Line
	 */
	public Block getNextBlock(int currBlock)
	{
		if (currBlock == 10) // requesting the next Block for the Red Line
			return theLine.get(8);
		else // requesting the next Block for the Green Line
			return theLine.get(64);
	}
	
	/**Method used to return the next Block from the current Block
	 * @param currBlock - The current Block from which you would like the next Block
	 * @param prevBlock - The Block that you just came from
	 * @return Returns the next Block based on your current Block, previous Block, and
	 * any Switch states.
	 */
	public Block getNextBlock(int currBlock, int prevBlock)
	{
		int connections[] = theLine.get(currBlock - 1).getConnections(); // Get the Block IDs of the Blocks that are connected to the current Block
		SwitchBlock theSwitch;
		if (name.equals("Green")) // Go here if the request is for the Green Line
		{
			if (prevBlock == 1) // Exiting the top loop
				prevBlock = 12;
			else if (prevBlock == 100) // Exiting the bottom loop
				prevBlock = 86;
			else if (prevBlock == 150) // Exiting the Switch connecting sections F, G, and Z
				prevBlock = 29;
			if (connections[0] == prevBlock) // Find the appropriate block to return from connections
				return theLine.get(connections[1] - 1);
			else
				return theLine.get(connections[0] - 1);
		}
		else // Request is for the Red Line
		{
			if (connections.length == 3) // The current Block is right before a Switch
			{
				if (prevBlock == 15 || prevBlock == 1) // Exiting the top loop
					return theLine.get(16);
				else if (prevBlock == 76 || prevBlock == 27) // Exiting the top jug handle at the top of section H
					return theLine.get(24);
				else if (prevBlock == 72 || prevBlock == 32) // Exiting the top jug handle in the middle of section H
					return theLine.get(33);
				else if (prevBlock == 71 || prevBlock == 38) // Exiting the bottom jug handle in the middle of section H
					return theLine.get(35);
				else if (prevBlock == 67 || prevBlock == 43) // Exiting the bottom jug handle at the bottom of section H
					return theLine.get(44);
				else if (prevBlock == 66 || prevBlock == 52) // Exiting the bottom loop
					return theLine.get(49);
				else if (prevBlock == 17) // Entering the top loop
				{
					theSwitch = (SwitchBlock)theLine.get(14);
					if (theSwitch.getSwitchState()) // check the Switch state
						return theLine.get(14);
					else
						return theLine.get(1);
				}
				else if (prevBlock == 25) // Entering the top jug handle at the top of section H
				{
					theSwitch = (SwitchBlock)theLine.get(26);
					if (theSwitch.getSwitchState()) // check the Switch state
						return theLine.get(26);
					else
						return theLine.get(75);
				}
				else if (prevBlock == 34) // Entering the top jug handle in the middle of section H
				{
					theSwitch = (SwitchBlock)theLine.get(31);
					if (theSwitch.getSwitchState()) // check the Switch state
						return theLine.get(31);
					else
						return theLine.get(71);
				}
				else if (prevBlock == 36) // Entering the bottom jug handle in the middle of section H
				{	
					theSwitch = (SwitchBlock)theLine.get(37);
					if (theSwitch.getSwitchState()) // check the Switch state
						return theLine.get(37);
					else
						return theLine.get(70);
				}
				else if (prevBlock == 45) // Entering the bottom jug handle at the bottom of section H
				{
					theSwitch = (SwitchBlock)theLine.get(42);
					if (theSwitch.getSwitchState()) // check the Switch state
						return theLine.get(42);
					else
						return theLine.get(66);
				}
				else if (prevBlock == 50) // Entering the bottom loop
				{
					theSwitch = (SwitchBlock)theLine.get(51);
					if (theSwitch.getSwitchState()) // check the Switch state
						return theLine.get(51);
					else
						return theLine.get(65);
				}
				else // Entering the Switch that goes back to the Yard
					return theLine.get(7);
			}		
			else // The current Block is a normal bidirectional piece of track
			{
				if (connections[0] == prevBlock)
					return theLine.get(connections[1] - 1);
				else
					return theLine.get(connections[0] - 1);
			}		
		}
	}
	
	/**
	 * @return Returns the number of Blocks that make up the Line
	 */
	public int getNumBlock()
	{
		return numBlocks;
	}
	
	/**
	 * @return Returns the number of Station Blocks that are in the Line
	 */
	public int getNumStations()
	{
		return numStations;
	}
	
	/**
	 * @return Returns the ArrayList of Train objects that exist on the Line
	 */
	public ArrayList<Train> getTrains()
	{
		return theTrains;
	}
	
	/**
	 * @return Returns the ArrayList of Controller Blocks that exist on the Line
	 */
	public ArrayList<ControllerBlock> getControllers()
	{
		return theControllers;
	}
	
	/**
	 * @return Returns the ArrayList of Station Blocks that exist on the Line
	 */
	public ArrayList<StationBlock> getStations()
	{
		return theStations;
	}
	
	/**
	 * @param theStation - The current Station from which you would like the next available Stations
	 * @return Returns an ArrayList of Station Blocks that can be travelled directly to from the current Station Block
	 */
	public ArrayList<StationBlock> getConnectingStations(StationBlock theStation)
	{
		int[] connections = theStation.getConnections();
		ArrayList<StationBlock> theConnections = new ArrayList<StationBlock>(connections.length);
		for (int i = 0; i < connections.length; i++)
		{
			theConnections.add(theStations.get(connections[i]));
		}
		return theConnections;
	}
	
	/**
	 * @param startBlock - Starting Block from which you would like the path to start
	 * @param endBlock - The Block you are trying to find a path to
	 * @param prevBlock - The Block that you just came from
	 * @return Returns an ArrayList of Block objects that make up the requested path
	 */
	public ArrayList<Block> getPath(int startBlock, int endBlock, int prevBlock)
	{
		ArrayList<Block> thePath = new ArrayList<Block>();
		while (startBlock != endBlock)
		{
			Block nextBlock = getNextBlock(startBlock, prevBlock);
			thePath.add(nextBlock);
			prevBlock = startBlock;
			startBlock = nextBlock.getBlockID();
			System.out.println(nextBlock.toString());
		}
		return thePath;
	}	
	
	/**
	 * @param theTrain - Train Model object to be added to the Train object
	 * @param currBlock - The current Block that the Train Model object is on
	 * @param prevBlock - The Block that the Train Model just came from
	 */
	public void addTrain(TrainModel theTrain, int currBlock, int prevBlock)
	{
		theTrains.add(new Train(theTrain, currBlock, prevBlock)); // create and add a new Train object to the ArrayList of Train objects
	}
	
	/**
	 * @author Jordan
	 * Inner class that defines a Train object. The purpose of a Train object is to hold a Train Model object and to provide some
	 * additional functionality that a Train Model does not have so that the Train Models can be manipulated as they move along
	 * the track
	 */
	public class Train 
	{
		public TrainModel theTrain;
		public Block currBlock, prevBlock;
		public double speed, locationOnBlock;
		boolean passengersNotLoaded = true;
		
		/**
		 * @param theTrain - the Train Model that will be associated with the Train object
		 * @param currBlock - The current Block that the Train Model is on
		 * @param prevBlock - The Block that the Train Model just came from
		 */
		public Train(TrainModel theTrain, int currBlock, int prevBlock)
		{
			this.theTrain = theTrain;
			this.currBlock = theLine.get(currBlock - 1);
			if (prevBlock != 0)
				this.prevBlock = theLine.get(prevBlock - 1);
			else
				this.prevBlock = theLine.get(0);

			theLine.get(currBlock - 1).hasTrain = true;
			theLine.get(currBlock - 1).addTrain(theTrain);
		}
		
		/**Method that updates the location of a Train Model object. It also takes care of loading passengers onto a Train Model
		 * if the Train Model object stops at a Station Block and sets the Transponder Signal of a Train Model if the Train Model
		 * will be entering a Station Block
		 * @param time - Time since the last displacement was calculated so that the new displacement can be calculated
		 */
		public void updateLocation(long time)
		{
			locationOnBlock += theTrain.getSpeed()*(((double) time)/1000);
			System.out.println("locationOnBlock: " + locationOnBlock);
			if (locationOnBlock > currBlock.length) // The Train Model has moved onto the next Block
			{
				Block iBlockTemp = currBlock;
				currBlock = getNextBlock(currBlock.blockID, prevBlock.blockID); // Get the next Block
				prevBlock = iBlockTemp;
				locationOnBlock -= prevBlock.length; // Set the location on the new Block
				prevBlock.removeTrain(); // Remove the Train Model from the now previous Block
				currBlock.addTrain(theTrain); // Add the Train Model to the now current Block
				theTrain.setCurrentBlock(currBlock); // Set the new current Block on the Train Model
				if (currBlock.hasTransponder && !(prevBlock instanceof StationBlock)) // Check to see if the Train Model will be entering a Station Block
				{
					StationBlock theStation = (StationBlock)getNextBlock(currBlock.getBlockID(), prevBlock.getBlockID());
					theTrain.setTransponderSignal(theStation.getStationName());	// Set the transponder signal on the Train Model
				}
				if (currBlock instanceof StationBlock) // Just entered a Station Block and passengers have not been loaded yet
					passengersNotLoaded = true;
				System.out.println("TrackModel: Train "+theTrain.getTrainID()+" moved from block "+prevBlock.getBlockID()+" to block "+currBlock.getBlockID());
			}
			if ((currBlock instanceof StationBlock) && theTrain.getDoorState().equals("OPEN") && passengersNotLoaded) // Check to see if passengers can be added yet
			{
				int numPassengers = randomNumber.nextInt(201); // Get a random number of passengers to add or remove from the Train Model
				numPassengers -= 100; // Range of -100 to 100
				int passengersOnTrain = theTrain.getNumPassengers();
				if (passengersOnTrain == 0 && numPassengers < 0) // If the Train Model is empty, change the negative number to a positive number
					theTrain.setPassengerFlow(numPassengers - 2*numPassengers);
				else if (numPassengers < 0 && Math.abs(numPassengers) > passengersOnTrain) // Empty the Train Model if the number of passengers to leave is greater
					theTrain.setPassengerFlow(passengersOnTrain - passengersOnTrain*2);	   // than the number of passengers currently on the Train Model	
				else if ((numPassengers + passengersOnTrain > Utilities.TrainConstants.MAX_PASSENGERS))		// Only allow up to the maximum capacity onto the Train Model
					theTrain.setPassengerFlow(Utilities.TrainConstants.MAX_PASSENGERS - passengersOnTrain);
				else // The random number of passengers to be added or removed will not cause any problems, so set the randomly generated number
					theTrain.setPassengerFlow(numPassengers);
				passengersNotLoaded = false;
			}
			Main.updateTrainPosition(theTrain.getTrainID(), currBlock.getBlockID()); // Update the Train Model's location in the GUI
		}
		
		/**
		 * @return Returns the current location on the current Block
		 */
		public double getLocationOnBlock()
		{
			return locationOnBlock;
		}
		
		/**
		 * @return Returns the Block that the Train is currently on
		 */
		public Block getCurrBlock()
		{
			return currBlock;
		}
	}
	
}
